import yaml from "yaml";
import fs from "fs";
import path from "path";
import type swaggerJSDoc from "swagger-jsdoc";
import { Project, SourceFile, StructureKind } from "ts-morph";

const yamlText = fs.readFileSync(
  path.join(process.cwd(), "./openapi.yaml"),
  "utf8"
);

type Parameter = swaggerJSDoc.Parameter;
type Reference = swaggerJSDoc.Reference;
type Schema = swaggerJSDoc.Schema;
type Components = swaggerJSDoc.Components;
type Paths = swaggerJSDoc.Paths;
type Operation = swaggerJSDoc.Operation;
type RequestBody = swaggerJSDoc.RequestBody;
type Response = swaggerJSDoc.Response;

const swaggerSpec = yaml.parse(yamlText) as {
  components: Components;
  paths: Paths;
};

const parameters = swaggerSpec.components.parameters;
// const requestBodies = swaggerSpec.components.requestBodies;
const schemas = swaggerSpec.components.schemas;
const paths = swaggerSpec.paths;

const schemaToTs = (schema: Schema): string => {
  if (!schema) return "{}";

  const inner = (): string => {
    if (schema.enum) {
      const enumValues = schema.enum as Array<string | number | boolean | null>;
      return enumValues
        .map((item) => (typeof item === "string" ? `"${item}"` : item))
        .join(" | ");
    }

    if (schema.$ref) {
      return schema.$ref.split("/").pop(); // 处理单个 $ref
    }

    if (schema.allOf) {
      return `(${schema.allOf
        .map((subSchema: Schema) => schemaToTs(subSchema))
        .join(" & ")})`;
    }
    if (schema.oneOf) {
      return `(${schema.oneOf
        .map((subSchema: Schema) => schemaToTs(subSchema))
        .join(" | ")})`;
    }
    if (schema.anyOf) {
      return `(${schema.anyOf
        .map((subSchema: Schema) => schemaToTs(subSchema))
        .join(" | ")})`;
    }

    if (schema.type === "object") {
      const properties = schema.properties || {};
      const requiredProps = schema.required || [];

      const propsStr = Object.entries(properties)
        .map(([propName, propSchema]) => {
          const optionalFlag = requiredProps.includes(propName) ? "" : "?";
          const typeStr = schemaToTs(propSchema as Schema);
          return `${propName}${optionalFlag}: ${typeStr};`;
        })
        .join(" ");

      return propsStr ? `{ ${propsStr} }` : "{}";
    }

    if (schema.type === "string") return "string";
    if (schema.type === "integer" || schema.type === "number") return "number";
    if (schema.type === "boolean") return "boolean";
    if (schema.type === "null") return "null";
    if (schema.type === "array") {
      return `${schemaToTs(schema.items)}[]`;
    }

    // 针对于 query 和 path 参数特殊处理
    if (schema.schema) {
      return schemaToTs(schema.schema);
    }

    return "any"; // 其他情况默认 any
  };

  const baseType = inner();

  return schema.nullable ? `${baseType} | null` : baseType;
};

const parameterToTs = (parameter: Parameter | Reference): string => {
  if (parameter.$ref) {
    return schemaToTs({ $ref: parameter.$ref });
  }
  const p = parameter as Parameter;
  // swagger 里面参数的 schema 一般都在 param.schema
  const typeStr = schemaToTs(p.schema);
  // path参数一定是必需的，query参数通常非必需
  const optionalFlag = p.required ? "" : "?";
  return `{ ${p.name}${optionalFlag}: ${typeStr} }`;
};

const run = async () => {
  const targetFilePath = path.join(process.cwd(), "./src/types/api.ts");
  // 初始化 ts-morph 项目
  const project = new Project();
  const file = project.createSourceFile(targetFilePath, "", {
    overwrite: true,
  });

  // 将公共的 parameters 转换成 TS 类型
  if (parameters) {
    for (const [name, parameter] of Object.entries(parameters)) {
      file.addTypeAlias({
        kind: StructureKind.TypeAlias,
        name,
        type: parameterToTs(parameter),
        isExported: true,
      });
    }
  }

  // 将公共的 schemas 转换成 TS 类型
  if (schemas) {
    for (const [name, schema] of Object.entries(schemas)) {
      if ("type" in schema && schema.type === "object") {
        file.addInterface({
          kind: StructureKind.Interface,
          name,
          properties: Object.entries(schema.properties).map(
            ([propName, propSchema]) => ({
              name: propName,
              type: schemaToTs(propSchema),
              hasQuestionToken: !schema.required?.includes(propName),
            })
          ),
          isExported: true,
        });
      } else {
        file.addTypeAlias({
          kind: StructureKind.TypeAlias,
          name,
          type: schemaToTs(schema),
          isExported: true,
        });
      }
    }
  }

  if (paths) {
    for (const path of Object.values(paths)) {
      for (const [method, operation] of Object.entries(path)) {
        const { operationId, parameters, requestBody, responses } =
          operation as Operation;
        if (operationId) {
          const requestType: string[] = [];
          if (parameters) {
            requestType.push(...parameters.map((p) => parameterToTs(p)));
          }
          const requestSchema = (requestBody as RequestBody)?.content?.[
            "application/json"
          ]?.schema;
          if (requestSchema) {
            requestType.push(schemaToTs(requestSchema));
          }
          if (requestType.length) {
            file.addTypeAlias({
              kind: StructureKind.TypeAlias,
              name: `${operationId}Request`,
              type: requestType.join(" & "),
              isExported: true,
            });
          }
          const responseSchema = responses?.["200"]?.content?.[
            "application/json"
          ]?.schema as Schema | Reference;
          if (responseSchema) {
            file.addTypeAlias({
              kind: StructureKind.TypeAlias,
              name: `${operationId}Response`,
              type: schemaToTs(responseSchema),
              isExported: true,
            });
          }
        }
      }
    }
  }

  file.addInterface({
    kind: StructureKind.Interface,
    name: "Response<T>",
    properties: [
      {
        name: "status",
        type: "boolean",
        hasQuestionToken: false,
      },
      {
        name: "code",
        type: "number",
        hasQuestionToken: false,
      },
      {
        name: "message",
        type: "string",
        hasQuestionToken: false,
      },
      {
        name: "data",
        type: "T",
        hasQuestionToken: true,
      },
    ],
    isExported: true,
  });

  await file.save();

  console.log(`create ${targetFilePath} successfully!`);
};

run();
